<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LLM 模型调用实战与提示工程</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        :root {
            --primary: #4361ee;
            --primary-dark: #3a56d4;
            --secondary: #3f37c9;
            --accent: #4895ef;
            --dark: #1a1a2e;
            --light: #f8f9fa;
        }
        
        body {
            font-family: 'Noto Sans SC', 'Noto Serif SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #333;
            line-height: 1.6;
            background-color: #f9fafb;
        }
        
        .hero {
            background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
            color: white;
        }
        
        .code-block {
            background-color: #2d3748;
            color: #e2e8f0;
            border-radius: 0.5rem;
            overflow-x: auto;
        }
        
        .nav-item:hover {
            color: var(--accent);
            transform: translateY(-2px);
        }
        
        .card {
            transition: all 0.3s ease;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
        }
        
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
        }
        
        .section-divider {
            height: 1px;
            background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.1), transparent);
        }
        
        .dropdown-content {
            display: none;
            opacity: 0;
            transition: opacity 0.3s ease;
        }
        
        .dropdown:hover .dropdown-content {
            display: block;
            opacity: 1;
        }
        
        .tab-content {
            display: none;
        }
        
        .tab-content.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }
        
        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        
        .floating-btn {
            animation: float 3s ease-in-out infinite;
        }
        
        @keyframes float {
            0% { transform: translateY(0px); }
            50% { transform: translateY(-10px); }
            100% { transform: translateY(0px); }
        }
    </style>
</head>
<body>
    <!-- Hero Section -->
    <section class="hero py-20 px-4 md:px-0">
        <div class="container mx-auto max-w-6xl text-center">
            <h1 class="text-4xl md:text-6xl font-bold mb-6 animate-bounce">LLM 模型调用实战</h1>
            <p class="text-xl md:text-2xl mb-8 opacity-90">SpringAI 核心接口使用与提示工程最佳实践</p>
            <div class="flex justify-center space-x-4">
                <a href="#chatclient" class="bg-white text-primary px-6 py-3 rounded-full font-medium hover:bg-opacity-90 transition-all">开始探索</a>
                <a href="#examples" class="border-2 border-white text-white px-6 py-3 rounded-full font-medium hover:bg-white hover:bg-opacity-10 transition-all">查看示例</a>
            </div>
        </div>
    </section>

    <!-- Navigation -->
    <nav class="sticky top-0 bg-white shadow-md z-50">
        <div class="container mx-auto max-w-6xl px-4 py-4 flex justify-between items-center">
            <a href="#" class="text-2xl font-bold text-primary">LLM Guide</a>
            <div class="hidden md:flex space-x-8">
                <a href="#chatclient" class="nav-item">ChatClient</a>
                <a href="#prompt" class="nav-item">提示工程</a>
                <a href="#vector" class="nav-item">向量存储</a>
                <div class="dropdown relative">
                    <a href="#" class="nav-item flex items-center">更多 <i class="fas fa-chevron-down ml-1 text-xs"></i></a>
                    <div class="dropdown-content absolute top-full left-0 mt-2 w-48 bg-white shadow-lg rounded-md py-2 z-10">
                        <a href="#sync-async" class="block px-4 py-2 hover:bg-gray-100">同步与异步</a>
                        <a href="#parameters" class="block px-4 py-2 hover:bg-gray-100">参数优化</a>
                        <a href="#multimodel" class="block px-4 py-2 hover:bg-gray-100">多模型切换</a>
                    </div>
                </div>
            </div>
            <button class="md:hidden text-gray-600">
                <i class="fas fa-bars text-2xl"></i>
            </button>
        </div>
    </nav>

    <!-- Main Content -->
    <main class="container mx-auto max-w-6xl px-4 py-12">
        <!-- ChatClient Section -->
        <section id="chatclient" class="mb-20">
            <div class="flex items-center mb-8">
                <div class="w-12 h-12 rounded-full bg-primary flex items-center justify-center text-white mr-4">
                    <i class="fas fa-comment-dots text-xl"></i>
                </div>
                <h2 class="text-3xl font-bold">ChatClient 接口</h2>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">核心接口结构</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">public interface ChatClient {
    // 使用字符串提示调用模型
    ChatResponse call(String message);
    
    // 使用结构化提示调用模型
    ChatResponse call(Prompt prompt);
    
    // 使用消息列表调用模型
    ChatResponse call(List&lt;Message&gt; messages);
    
    // 流式响应方法
    Publisher&lt;ChatResponse&gt; stream(Prompt prompt);
}</code></pre>
                    </div>
                    <p class="text-gray-700">SpringAI的核心接口，提供了与各种LLM交互的统一抽象层。</p>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">常用实现类</h3>
                    <ul class="space-y-3">
                        <li class="flex items-center">
                            <i class="fas fa-check-circle text-green-500 mr-2"></i>
                            <span><code>OpenAiChatClient</code>：连接 OpenAI 模型（如 GPT-4）</span>
                        </li>
                        <li class="flex items-center">
                            <i class="fas fa-check-circle text-green-500 mr-2"></i>
                            <span><code>AzureOpenAiChatClient</code>：连接 Azure OpenAI 服务</span>
                        </li>
                        <li class="flex items-center">
                            <i class="fas fa-check-circle text-green-500 mr-2"></i>
                            <span><code>AnthropicChatClient</code>：连接 Anthropic 模型（如 Claude）</span>
                        </li>
                        <li class="flex items-center">
                            <i class="fas fa-check-circle text-green-500 mr-2"></i>
                            <span><code>OllamaChatClient</code>：连接本地部署的开源 Ollama 模型</span>
                        </li>
                    </ul>
                </div>
            </div>
            
            <div class="mb-12">
                <h3 class="text-2xl font-semibold mb-6">基本用法示例</h3>
                <div class="tabs flex border-b border-gray-200 mb-6">
                    <button class="tab-btn active px-4 py-2 font-medium text-primary border-b-2 border-primary" data-tab="basic">简单提示</button>
                    <button class="tab-btn px-4 py-2 font-medium text-gray-600" data-tab="structured">结构化提示</button>
                    <button class="tab-btn px-4 py-2 font-medium text-gray-600" data-tab="response">处理返回值</button>
                </div>
                
                <div id="basic" class="tab-content active">
                    <div class="code-block p-4">
                        <pre><code class="text-sm">@Service
public class SimpleAiService {
    private final ChatClient chatClient;
    
    @Autowired
    public SimpleAiService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    // 简单提示
    public String getSimpleResponse(String userQuery) {
        ChatResponse response = chatClient.call(userQuery);
        return response.getResult().getOutput().getContent();
    }
}</code></pre>
                    </div>
                </div>
                
                <div id="structured" class="tab-content">
                    <div class="code-block p-4">
                        <pre><code class="text-sm">@Service
public class SimpleAiService {
    private final ChatClient chatClient;
    
    // 使用结构化提示
    public String getStructuredResponse(String userQuery) {
        // 创建用户消息
        UserMessage userMessage = new UserMessage(userQuery);
        
        // 创建系统消息
        SystemMessage systemMessage = new SystemMessage("你是一个专业的Java开发助手");
        
        // 创建提示
        Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
        
        // 调用模型
        ChatResponse response = chatClient.call(prompt);
        
        return response.getResult().getOutput().getContent();
    }
}</code></pre>
                    </div>
                </div>
                
                <div id="response" class="tab-content">
                    <div class="code-block p-4">
                        <pre><code class="text-sm">ChatResponse response = chatClient.call("Java中如何实现线程安全？");

// 获取生成的文本内容
String content = response.getResult().getOutput().getContent();

// 获取元数据
Map&lt;String, Object&gt; metadata = response.getMetadata();

// 获取令牌使用情况
int promptTokens = response.getMetadata().getUsage().getPromptTokens();
int completionTokens = response.getMetadata().getUsage().getCompletionTokens();</code></pre>
                    </div>
                </div>
            </div>
        </section>

        <!-- Sync & Async Section -->
        <section id="sync-async" class="mb-20">
            <div class="flex items-center mb-8">
                <div class="w-12 h-12 rounded-full bg-primary flex items-center justify-center text-white mr-4">
                    <i class="fas fa-bolt text-xl"></i>
                </div>
                <h2 class="text-3xl font-bold">同步与异步调用</h2>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">同步调用</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">// 同步调用 - 阻塞直到收到完整响应
ChatResponse response = chatClient.call("解释Java中的泛型");
String answer = response.getResult().getOutput().getContent();</code></pre>
                    </div>
                    <p class="text-gray-700">适合简单使用场景，直接返回完整响应。</p>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">异步调用</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">@Service
public class AsyncAiService {
    private final ChatClient chatClient;
    private final AsyncTaskExecutor asyncExecutor;
    
    @Autowired
    public AsyncAiService(ChatClient chatClient, 
                        @Qualifier("aiTaskExecutor") AsyncTaskExecutor asyncExecutor) {
        this.chatClient = chatClient;
        this.asyncExecutor = asyncExecutor;
    }
    
    // 使用CompletableFuture异步处理
    public CompletableFuture&lt;String&gt; getAsyncResponse(String query) {
        return CompletableFuture.supplyAsync(() -> {
            ChatResponse response = chatClient.call(query);
            return response.getResult().getOutput().getContent();
        }, asyncExecutor);
    }
}</code></pre>
                    </div>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">配置自定义线程池</h3>
                    <div class="code-block p-4">
                        <pre><code class="text-sm">@Configuration
public class AsyncConfig {
    @Bean(name = "aiTaskExecutor")
    public Executor aiTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("ai-async-");
        executor.initialize();
        return executor;
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">流式处理</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">@Service
public class StreamingAiService {
    private final ChatClient chatClient;
    
    @Autowired
    public StreamingAiService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    // 流式API返回
    public Flux&lt;String&gt; getStreamingResponse(String query) {
        UserMessage userMessage = new UserMessage(query);
        Prompt prompt = new Prompt(List.of(userMessage));
        
        return Flux.from(chatClient.stream(prompt))
            .map(response -> response.getResult().getOutput().getContent());
    }
}</code></pre>
                    </div>
                </div>
            </div>
        </section>

        <!-- Parameters Section -->
        <section id="parameters" class="mb-20">
            <div class="flex items-center mb-8">
                <div class="w-12 h-12 rounded-full bg-primary flex items-center justify-center text-white mr-4">
                    <i class="fas fa-sliders-h text-xl"></i>
                </div>
                <h2 class="text-3xl font-bold">模型参数优化</h2>
            </div>
            
            <div class="card bg-white p-6 rounded-lg mb-12">
                <h3 class="text-xl font-semibold mb-4 text-primary">参数优化示例</h3>
                <div class="code-block p-4 mb-4">
                    <pre><code class="text-sm">@Service
public class OptimizedAiService {
    private final ChatClient chatClient;
    
    public String getCreativeResponse(String query) {
        UserMessage userMessage = new UserMessage(query);
        
        // 创建模型参数选项
        ChatOptions options = ChatOptions.builder()
            .withTemperature(0.8)    // 增加创造性
            .withTopP(0.9)           // 调整采样方式
            .withMaxTokens(500)      // 限制回答长度
            .build();
        
        // 创建带选项的提示
        Prompt prompt = new Prompt(List.of(userMessage), options);
        
        // 调用模型
        ChatResponse response = chatClient.call(prompt);
        return response.getResult().getOutput().getContent();
    }
}</code></pre>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">主要模型参数说明</h3>
                    <div class="space-y-4">
                        <div>
                            <h4 class="font-medium">Temperature（温度，0.0-2.0）</h4>
                            <ul class="list-disc pl-5 mt-2 text-gray-700">
                                <li>较低值（0.1-0.3）：输出更确定、一致、可预测，适合代码生成、事实检索</li>
                                <li>较高值（0.7-1.0）：输出更随机、创意、多样，适合创意写作、头脑风暴</li>
                            </ul>
                        </div>
                        <div>
                            <h4 class="font-medium">Top P（核心采样，0.0-1.0）</h4>
                            <p class="mt-2 text-gray-700">限制令牌选择范围，只考虑累积概率达到p值的令牌。0.1表示只考虑最高概率的令牌，0.9则考虑更广泛的可能性。</p>
                        </div>
                    </div>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <div class="space-y-4">
                        <div>
                            <h4 class="font-medium">Max Tokens（最大令牌数）</h4>
                            <p class="mt-2 text-gray-700">限制响应长度，对API成本控制很重要。计算方法：4个中文字符≈1个token，75个英文单词≈100个token。</p>
                        </div>
                        <div>
                            <h4 class="font-medium">Presence/Frequency Penalty（存在/频率惩罚，-2.0-2.0）</h4>
                            <ul class="list-disc pl-5 mt-2 text-gray-700">
                                <li>Presence：正值减少模型讨论新主题的倾向</li>
                                <li>Frequency：正值减少单词、短语重复</li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Prompt Engineering Section -->
        <section id="prompt" class="mb-20">
            <div class="flex items-center mb-8">
                <div class="w-12 h-12 rounded-full bg-primary flex items-center justify-center text-white mr-4">
                    <i class="fas fa-magic text-xl"></i>
                </div>
                <h2 class="text-3xl font-bold">提示工程应用</h2>
            </div>
            
            <div class="card bg-white p-6 rounded-lg mb-12">
                <h3 class="text-xl font-semibold mb-4 text-primary">提示模板设计</h3>
                <div class="code-block p-4 mb-4">
                    <pre><code class="text-sm">@Service
public class PromptTemplateService {
    // 基础提示模板
    public String generateBasicPrompt(String input) {
        PromptTemplate template = new PromptTemplate("""
            你是一名Java专家，请回答以下关于Java的问题:
            {input}
            回答要简洁、准确，并提供代码示例。
            """);
        
        Map&lt;String, Object&gt; parameters = Map.of("input", input);
        return template.render(parameters);
    }
}</code></pre>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">变量注入</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">@Service
public class DynamicPromptService {
    private final ChatClient chatClient;
    
    public String generateDescription(String product, String audience, int wordCount) {
        String templateText = """
            生成一个关于{product}的产品描述。
            目标受众是{audience}。
            描述应该大约{wordCount}字，语调要{tone}。
            """;
        
        PromptTemplate template = new PromptTemplate(templateText);
        
        Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();
        params.put("product", product);
        params.put("audience", audience);
        params.put("wordCount", wordCount);
        params.put("tone", wordCount &lt; 100 ? "简洁" : "详细");
        
        String renderedPrompt = template.render(params);
        return chatClient.call(renderedPrompt).getResult().getOutput().getContent();
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">多轮对话</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">@Service
public class ChatSessionService {
    private final ChatClient chatClient;
    private final Map&lt;String, List&lt;Message&gt;&gt; sessionMessages;
    
    public String sendMessage(String sessionId, String userMessage) {
        // 获取或创建会话
        List&lt;Message&gt; messages = sessionMessages.computeIfAbsent(
            sessionId, id -&gt; new ArrayList&lt;&gt;(List.of(new SystemMessage("你是一个有帮助的助手。"))
        );
        
        // 添加用户消息
        UserMessage newUserMessage = new UserMessage(userMessage);
        messages.add(newUserMessage);
        
        // 创建包含完整对话历史的提示
        Prompt prompt = new Prompt(new ArrayList&lt;&gt;(messages));
        
        // 调用AI获取响应
        ChatResponse response = chatClient.call(prompt);
        String responseContent = response.getResult().getOutput().getContent();
        
        // 将AI回复添加到会话历史
        messages.add(new AiMessage(responseContent));
        
        return responseContent;
    }
}</code></pre>
                    </div>
                </div>
            </div>
            
            <div class="mb-12">
                <h3 class="text-2xl font-semibold mb-6">特定场景提示优化</h3>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                    <div class="card bg-white p-6 rounded-lg hover:border-primary border border-transparent">
                        <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-primary mb-4">
                            <i class="fas fa-code"></i>
                        </div>
                        <h4 class="text-lg font-semibold mb-2">代码生成</h4>
                        <p class="text-gray-700">使用较低温度值确保代码准确性，提供清晰的规格说明和示例。</p>
                    </div>
                    <div class="card bg-white p-6 rounded-lg hover:border-primary border border-transparent">
                        <div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center text-green-500 mb-4">
                            <i class="fas fa-chart-bar"></i>
                        </div>
                        <h4 class="text-lg font-semibold mb-2">数据分析</h4>
                        <p class="text-gray-700">明确指定数据格式和分析目标，要求结构化输出。</p>
                    </div>
                    <div class="card bg-white p-6 rounded-lg hover:border-primary border border-transparent">
                        <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center text-purple-500 mb-4">
                            <i class="fas fa-comments"></i>
                        </div>
                        <h4 class="text-lg font-semibold mb-2">客服对话</h4>
                        <p class="text-gray-700">结合用户上下文和历史对话，保持一致的语调和风格。</p>
                    </div>
                </div>
            </div>
        </section>

        <!-- Vector Storage Section -->
        <section id="vector" class="mb-20">
            <div class="flex items-center mb-8">
                <div class="w-12 h-12 rounded-full bg-primary flex items-center justify-center text-white mr-4">
                    <i class="fas fa-database text-xl"></i>
                </div>
                <h2 class="text-3xl font-bold">向量存储与检索</h2>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">文本向量化</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">@Service
public class EmbeddingService {
    private final EmbeddingClient embeddingClient;
    
    // 单个文本向量化
    public List&lt;Double&gt; embedText(String text) {
        EmbeddingResponse response = embeddingClient.embed(text);
        return response.getResult().getOutput().getEmbedding();
    }
    
    // 计算向量余弦相似度
    private double cosineSimilarity(List&lt;Double&gt; vec1, List&lt;Double&gt; vec2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        for (int i = 0; i &lt; vec1.size(); i++) {
            dotProduct += vec1.get(i) * vec2.get(i);
            norm1 += Math.pow(vec1.get(i), 2);
            norm2 += Math.pow(vec2.get(i), 2);
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="card bg-white p-6 rounded-lg">
                    <h3 class="text-xl font-semibold mb-4 text-primary">文档分块策略</h3>
                    <div class="code-block p-4 mb-4">
                        <pre><code class="text-sm">public List&lt;TextBlock&gt; splitDocument(String document, int blockSize, int overlap) {
    List&lt;TextBlock&gt; blocks = new ArrayList&lt;&gt;();
    
    // 简单的基于字符的分块
    for (int i = 0; i &lt; document.length(); i += (blockSize - overlap)) {
        int end = Math.min(i + blockSize, document.length());
        String blockText = document.substring(i, end);
        
        // 如果不是在文档开头，尝试找到句子或段落边界
        if (i &gt; 0) {
            for (int j = Math.min(overlap, blockText.length() / 4); j &gt;= 0; j--) {
                if (j &lt; blockText.length() &amp;&amp; 
                    (blockText.charAt(j) == ' ' &amp;&amp; j &gt; 0 &amp;&amp; 
                     (blockText.charAt(j-1) == '.' || 
                      blockText.charAt(j-1) == '?' || 
                      blockText.charAt(j-1) == '!'))) {
                    blockText = blockText.substring(j + 1);
                    i += (j + 1);
                    break;
                }
            }
        }
        
        blocks.add(new TextBlock(blockText, i));
    }
    
    return blocks;
}</code></pre>
                    </div>
                </div>
            </div>
            
            <div class="mb-12">
                <h3 class="text-2xl font-semibold mb-6">向量数据库选择</h3>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                    <div class="card bg-white p-6 rounded-lg">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center text-red-500 mr-3">
                                <i class="fab fa-redis"></i>
                            </div>
                            <h4 class="text-lg font-semibold">Redis</h4>
                        </div>
                        <p class="text-gray-700 mb-4">内存数据库，快速检索，适合中小规模数据。</p>
                        <div class="code-block p-2 text-xs">
                            <pre><code>spring.ai.vectorstore.type=redis</code></pre>
                        </div>
                    </div>
                    <div class="card bg-white p-6 rounded-lg">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-500 mr-3">
                                <i class="fas fa-database"></i>
                            </div>
                            <h4 class="text-lg font-semibold">PostgreSQL</h4>
                        </div>
                        <p class="text-gray-700 mb-4">关系型数据库，支持pgvector扩展，适合已有PG环境。</p>
                        <div class="code-block p-2 text-xs">
                            <pre><code>spring.ai.vectorstore.type=pgvector</code></pre>
                        </div>
                    </div>
                    <div class="card bg-white p-6 rounded-lg">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center text-purple-500 mr-3">
                                <i class="fas fa-server"></i>
                            </div>
                            <h4 class="text-lg font-semibold">Milvus</h4>
                        </div>
                        <p class="text-gray-700 mb-4">专用向量数据库，支持大规模数据和高性能检索。</p>
                        <div class="code-block p-2 text-xs">
                            <pre><code>spring.ai.vectorstore.type=milvus</code></pre>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="card bg-white p-6 rounded-lg">
                <h3 class="text-xl font-semibold mb-4 text-primary">相似度搜索实现</h3>
                <div class="code-block p-4 mb-4">
                    <pre><code class="text-sm">@Service
public class SimilaritySearchService {
    private final VectorStore vectorStore;
    
    // 基本相似度搜索
    public List&lt;SearchResult&gt; searchSimilarDocuments(String query, int k) {
        List&lt;Document&gt; results = vectorStore.similaritySearch(query, k);
        
        return results.stream()
            .map(doc -&gt; new SearchResult(
                doc.getId(), 
                doc.getContent(), 
                doc.getMetadata(), 
                doc.getScore())
            )
            .collect(Collectors.toList());
    }
}</code></pre>
                </div>
            </div>
        </section>
    </main>

    <!-- Footer -->
    <footer class="bg-gray-900 text-gray-300 py-12">
        <div class="container mx-auto max-w-6xl px-4">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-6 md:mb-0">
                    <h3 class="text-xl font-bold mb-2">技术小馆</h3>
                    <p class="text-gray-400">探索前沿技术，分享开发经验</p>
                </div>
                <div>
                    <a href="http://www.yuque.com/jtostring" class="text-primary hover:text-primary-light transition-colors">
                        <i class="fas fa-link mr-2"></i> www.yuque.com/jtostring
                    </a>
                </div>
            </div>
        </div>
    </footer>

    <!-- Floating Action Button -->
    <div class="fixed bottom-8 right-8">
        <a href="#" class="floating-btn w-14 h-14 bg-primary rounded-full flex items-center justify-center text-white shadow-lg hover:bg-primary-dark transition-colors">
            <i class="fas fa-arrow-up text-xl"></i>
        </a>
    </div>

    <script>
        // Tab functionality
        document.addEventListener('DOMContentLoaded', function() {
            const tabBtns = document.querySelectorAll('.tab-btn');
            
            tabBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    // Remove active class from all buttons and content
                    document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
                    document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
                    
                    // Add active class to clicked button
                    this.classList.add('active');
                    
                    // Show corresponding content
                    const tabId = this.getAttribute('data-tab');
                    document.getElementById(tabId).classList.add('active');
                });
            });
            
            // Smooth scrolling for anchor links
            document.querySelectorAll('a[href^="#"]').forEach(anchor => {
                anchor.addEventListener('click', function(e) {
                    e.preventDefault();
                    
                    const targetId = this.getAttribute('href');
                    const targetElement = document.querySelector(targetId);
                    
                    window.scrollTo({
                        top: targetElement.offsetTop - 80,
                        behavior: 'smooth'
                    });
                });
            });
            
            // Initialize Mermaid diagrams
            mermaid.initialize({
                startOnLoad: true,
                theme: 'default',
                flowchart: {
                    useMaxWidth: false,
                    htmlLabels: true
                }
            });
        });
    </script>
</body>
</html>